// msock.cpp
//
//
#include "msock.h"
#include "mlog.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <unistd.h>
#include <netdb.h>
#include <errno.h>
#include "m.h"

CMSocket::CMSocket()
{
    _sfd = -1;
    _efd = -1;
    _pCM = NULL;
    //_version = 1;
}

CMSocket::~CMSocket()
{
    _sfd = -1;
    _efd = -1;
    _pCM = NULL;
}

int CMSocket::CreateAndBindToLocal(const char *port)
{
    struct addrinfo hints = {0};
    hints.ai_family= AF_UNSPEC;/* Return IPv4 and IPv6 */
    hints.ai_socktype= SOCK_STREAM;/* TCP socket */
    hints.ai_flags= AI_PASSIVE;/* All interfaces */

    struct addrinfo *result = NULL;
    int s = getaddrinfo(NULL, port, &hints, &result); //more info about getaddrinfo() please see:man getaddrinfo!
    if(s != 0)
    {
        CMLog::Plog("getaddrinfo: %s\n", gai_strerror(s));
        return -1;
    }

    int sfd = -1;
    struct addrinfo *rp = NULL;
    for(rp=result; rp!=NULL; rp=rp->ai_next)
    {
            sfd = socket(rp->ai_family,rp->ai_socktype,rp->ai_protocol);
            if(sfd==-1)
                continue;
            printf("family:%d, socktype:%d, protocol:%d\n", rp->ai_family, rp->ai_socktype, rp->ai_protocol);
            if (rp->ai_addr)
            {
                        s =bind(sfd,rp->ai_addr,rp->ai_addrlen);
                        struct sockaddr_in *sin;
                        sin = (struct sockaddr_in*)rp->ai_addr;
                        char addr[20] = {0};
                        inet_ntop(AF_INET, &sin->sin_addr, addr, INET_ADDRSTRLEN); 
                        printf("address: %s\n", addr);
                    }
            if(s ==0)
            {
                        printf("bind to port:%s successful!\n", port);
                        /* We managed to bind successfully! */
                        break;
                    }
            close(sfd);
        }

    if(rp== NULL)
    {
            fprintf(stderr,"Could not bind\n");
            return-1;
        }
    freeaddrinfo(result);
    printf("sfd: %d\n", sfd);
    return sfd;
}

#define MAXEVENTS   64
int CMSocket::EPoll()
{
    CMLog log("CMSocket::EPoll");
    _efd = epoll_create1(0);
    if (_efd == -1)
    {
        CMLog::Plog("epoll_create failed!");
        return -1;
    }

    struct epoll_event evt;
    evt.data.fd = _sfd;
    evt.events = EPOLLIN|EPOLLET;
    if (epoll_ctl(_efd, EPOLL_CTL_ADD, _sfd, &evt) == -1)
    {
        CMLog::Plog("epoll_ctl failed!");
        return -1;
    }

    struct epoll_event *pevts = NULL;
    pevts = (epoll_event*)calloc(MAXEVENTS, sizeof(evt));

    CMLog::Plog("EPOLLERR=%d, EPOLLHUP=%d, EPOLLIN=%d", EPOLLERR, EPOLLHUP, EPOLLIN);

    bool loop = true;
    while (loop)
    {
        int n = epoll_wait(_efd, pevts, MAXEVENTS, -1);
        CMLog::Plog("receive events : %d", n);
        for (int i=0; i<n; i++)
        {
            CMLog::Plog("i: %d, e: %d, _sfd: %d fd: %d", i, pevts[i].events, _sfd, pevts[i].data.fd);
            if (pevts[i].events&EPOLLERR)
            {
                CMLog::Plog("CMSocket, EPOLLERR");
                close(pevts[i].data.fd);
                continue;
            }
            else if (pevts[i].events&EPOLLHUP)
            {
                CMLog::Plog("CMSocket, EPOLLHUP");
                close(pevts[i].data.fd);
                continue;
            }
            else if (pevts[i].events&EPOLLIN == 0)
            {
                CMLog::Plog("CMSocket, EPOLLIN!");
                close(pevts[i].data.fd);
                continue;
            }
            else if (_sfd == pevts[i].data.fd)
            {
                // new client come in
       //         CMLog::Plog("accept new client");
                AcceptEPollClient(pevts[i]);
            }
            else
            {
                // receive client data
                CMLog::Plog("receive data from client");
                if (_pCM)
                {
                    if (88 == ((CM*)_pCM)->Do(pevts[i].data.fd))
                        loop = false;;
                }

            }
        }
    }
    free(pevts);
    end();

    return 0;
}

void CMSocket::AcceptEPollClient(struct epoll_event &event)
{
    while (1)
    {
        struct sockaddr in_addr = {0};
        socklen_t in_len = sizeof(in_addr);
        int infd = accept(_sfd, &in_addr, &in_len);
        if (infd == -1)
        {
            if ((errno == EAGAIN)
                    || (errno == EWOULDBLOCK))
            {
                break;
            }
            else
            {
                CMLog::Plog("CMSocket::Accept, accept client failed!");
                break;
            }
        }

        char hbuf[NI_MAXHOST] = {0};
        char sbuf[NI_MAXSERV] = {0};
        int s = getnameinfo(&in_addr, in_len, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), NI_NUMERICHOST|NI_NUMERICSERV);
        if (0 == s)
        {
            CMLog::Plog("Add new client: %d [%s:%s]", infd, hbuf, sbuf);
        }

        if (-1 == MakeNonBlocking(infd))
        {
            CMLog::Plog("MakeNonBlocking failed");
            return;
        }

        event.data.fd = infd;
        event.events = EPOLLIN|EPOLLET;
        if (-1 == epoll_ctl(_efd, EPOLL_CTL_ADD, infd, &event))
        {
            CMLog::Plog("add new client with epoll_ctl failed");
            return;
        }
    }
}

int CMSocket::MakeNonBlocking(int fd)
{
    int flags = fcntl(fd, F_GETFL, 0);
    if (-1 == flags)
    {
        CMLog::Plog("MakeNonBlocking fcntl failed");
        return -1;
    }
    flags |= O_NONBLOCK;
    
    int s = fcntl(fd, F_SETFL, flags);
    if (-1 == s)
    {
        CMLog::Plog("MakeNonBlocking fcntl failed 2");
    }

    return 0;
}

int CMSocket::start(void *pCM, const char *ip, const char *port)
{
    if (!pCM || !ip || !port)
        return -1;
    _pCM = pCM;

    _sfd = socket(AF_INET, SOCK_STREAM, 0);
    if (_sfd == -1)
    {
        CMLog::Plog("create server socket failed");
        return -1;
    }

    struct sockaddr_in sin = {0};
    sin.sin_family = AF_INET;
    sin.sin_port = htons(atoi(port));
    sin.sin_addr.s_addr = inet_addr(ip);
    if (bind(_sfd, (struct sockaddr*)&sin, sizeof(sin)) != 0)
    {
        CMLog::Plog("bind server address failed");
        return -1;
    }

    MakeNonBlocking(_sfd);
    
    if (listen(_sfd, SOMAXCONN) == -1)
    {
        CMLog::Plog("listen failed");
        return -1;
    }

    EPoll();

    return 0;
}

void CMSocket::end()
{
    CMLog::Plog("CMSocket::end");
    close(_efd);
    close(_sfd);
}

